package com.zjun.widget;

import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_DOWN;
import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_UP;
import static ohos.multimodalinput.event.TouchEvent.POINT_MOVE;
import static ohos.multimodalinput.event.TouchEvent.CANCEL;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ScrollHelper;
import ohos.agp.components.VelocityDetector;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.NotExistException;
import ohos.global.resource.Resource;
import ohos.global.resource.WrongTypeException;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;

/**
 * MoneySelectRuleView
 * 金额选择卷尺控件
 * <p>
 * 每隔固定100
 * <p>
 * 参考：{@link RuleView}
 * <p>
 * Author: Ralap
 * Description:
 * Date 2018/8/9
 */
public class MoneySelectRuleView extends Component {
    private static final boolean LOG_ENABLE = BuildConfig.DEBUG;

    /**
     * 滑动阈值
     */
    private final int TOUCH_SLOP;
    /**
     * 惯性滑动最小、最大速度
     */
    private final int MIN_FLING_VELOCITY;

    private final int MAX_FLING_VELOCITY;

    private int bgColor;
    private int gradationColor;
    private float gradationHeight;
    private float gradationShortLen;
    private float gradationLongLen;
    private float gradationShortWidth;
    private float gradationLongWidth;
    private float gradationValueGap;
    private float gradationTextSize;
    private int gradationTextColor;

    private float balanceTextSize;
    private int indicatorColor;
    private float unitGap;

    private String balanceText;
    private float balanceGap;

    private int maxValue;
    private int currentValue;
    private int balanceValue;
    private int valueUnit;
    private int valuePerCount;

    private float mCurrentDistance;
    private int mWidthRangeValue;
    private int mRangeDistance;

    private int mWidth;
    private int mHeight;
    private int mHalfWidth;
    private Paint mPaint;
    private Paint mTextPaint;
    private ScrollHelper mScroller;
    private VelocityDetector mVelocityTracker;
    private OnValueChangedListener mListener;

    /**
     * 数值变化监听器
     */
    public interface OnValueChangedListener {
        /**
         * 当值变化时调用
         *
         * @param newValue 变化后的新值
         */
        void onValueChanged(int newValue);
    }

    public MoneySelectRuleView(Context context) {
        this(context, null);
    }

    public MoneySelectRuleView(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    public MoneySelectRuleView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initAttrs(attrSet);

        // 初始化final常量，必须在构造中赋初值
        TOUCH_SLOP = 8;
        MIN_FLING_VELOCITY = 50;
        MAX_FLING_VELOCITY = 8000;

        calculateValues();
        init(context);
        addDrawTask(
                new DrawTask() {
                    @Override
                    public void onDraw(Component component, Canvas canvas) {
                        mWidth = component.getWidth();
                        mHalfWidth = mWidth >> 1;
                        mHeight = dp2px(60);
                        gradationHeight = dp2px(40);
                        mWidthRangeValue = (int) (mWidth / unitGap * valueUnit);
                        setHeight(mHeight);
                        // 背景
                        Paint paintBg = new Paint();
                        paintBg.setColor(new Color(bgColor));
                        RectFloat rect = new RectFloat(0, 0, mWidth, mHeight);
                        canvas.drawRect(rect, paintBg);
                        // 刻度数值
                        drawRule(canvas);
                        // 绘制指针
                        drawIndicator(canvas);
                    }
                });

        setTouchEventListener(
                new TouchEventListener() {
                    @Override
                    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                        final int action = touchEvent.getAction();
                        final int xCoordinate = (int) touchEvent.getPointerPosition(0).getX();
                        final int yCoordinate = (int) touchEvent.getPointerPosition(0).getY();
                        if (mVelocityTracker == null) {
                            mVelocityTracker = VelocityDetector.obtainInstance();
                        }
                        mVelocityTracker.addEvent(touchEvent);

                        switch (action) {
                            case PRIMARY_POINT_DOWN:
                                dealDown(xCoordinate, yCoordinate);
                                break;
                            case POINT_MOVE:
                                dealMove(xCoordinate, yCoordinate);
                                break;

                            case PRIMARY_POINT_UP:
                                dealUp(action);
                                break;

                            case CANCEL:
                                if (!mScroller.isFinished()) {
                                    // 结束滑动，并把数值立马置为终点值
                                    mScroller.abortAnimation();
                                }
                                break;
                            default:
                                break;
                        }
                        mLastX = xCoordinate;
                        mLastY = yCoordinate;
                        return true;
                    }
                });
    }

    private void dealUp(int action) {
        logD("onTouchEvent: action=%d", action);
        if (!mIsMoving) {
            return;
        }
        // 计算速度
        mVelocityTracker.calculateCurrentVelocity(1000, MAX_FLING_VELOCITY, MAX_FLING_VELOCITY);
        // 获取当前的水平速度
        int xVelocity = (int) mVelocityTracker.getHorizontalVelocity();
        logD("up: xVelocity=%d", xVelocity);
        if (Math.abs(xVelocity) < MIN_FLING_VELOCITY) {
            // 滑动刻度
            scrollToGradation();
        } else {
            // 惯性滑动。
            mScroller.doFling(
                    (int) mCurrentDistance, 0, -xVelocity, 0, 0, (int) mRangeDistance, 0, 0);
            scrollToGradation();
            invalidate();
        }
    }

    private void dealMove(int xCoordinate, int yCoordinate) {
        final int dx = xCoordinate - mLastX;
        if (!mIsMoving) {
            final int dy = yCoordinate - mLastY;
            if (Math.abs(xCoordinate - mDownX) <= TOUCH_SLOP || Math.abs(dx) < Math.abs(dy)) {
                return;
            }
            mIsMoving = true;
        }
        mCurrentDistance -= dx;
        computeValue();
    }

    private void dealDown(int xCoordinate, int yCoordinate) {
        mIsMoving = false;
        mDownX = xCoordinate;
        mDownY = yCoordinate;
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
        }
    }

    private void calculateValues() {
        mCurrentDistance = (float) currentValue / valueUnit * unitGap;
        mRangeDistance = (int) (maxValue / valueUnit * unitGap);
        mWidthRangeValue = (int) (mWidth / unitGap * valueUnit);
    }

    private void init(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(new Color(gradationColor));

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize((int) balanceTextSize);
        mTextPaint.setColor(new Color(gradationTextColor));

        mScroller = new ScrollHelper();

        mVelocityTracker = VelocityDetector.obtainInstance();
    }

    private void initAttrs(AttrSet attrSet) {
        bgColor = AttrUtils.getColorFromAttr(attrSet, "zjun_bgColor", Color.getIntColor("#F5F5F5"));
        gradationColor = AttrUtils.getColorFromAttr(attrSet, "zjun_gradationColor", Color.LTGRAY.getValue());
        gradationHeight = AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationHeight", dp2px(40));
        gradationShortLen = AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationShortLen", dp2px(6));
        gradationLongLen =
                AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationLongLen", (int) (gradationShortLen * 2));
        gradationShortWidth = AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationShortWidth", 1);
        gradationLongWidth =
                AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationLongWidth", (int) gradationShortWidth);
        gradationValueGap = AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationValueGap", dp2px(8));
        gradationTextSize = AttrUtils.getDimensionFromAttr(attrSet, "msrv_gradationTextSize", sp2px(12));
        gradationTextColor = AttrUtils.getColorFromAttr(attrSet, "zjun_textColor", Color.GRAY.getValue());
        indicatorColor = AttrUtils.getColorFromAttr(attrSet, "zjun_indicatorLineColor", Color.getIntColor("#eb4c1c"));
        balanceTextSize = AttrUtils.getDimensionFromAttr(attrSet, "msrv_balanceTextSize", sp2px(10));
        unitGap = AttrUtils.getDimensionFromAttr(attrSet, "msrv_unitGap", dp2px(6));
        balanceText = AttrUtils.getStringFromAttr(attrSet, "msrv_balanceTextSize", "");
        if (TextTool.isNullOrEmpty(balanceText)) {
            ohos.global.resource.ResourceManager resManager = getContext().getResourceManager();
            try {
                balanceText = resManager.getElement(ResourceTable.String_balance_text).getString();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NotExistException e) {
                e.printStackTrace();
            } catch (WrongTypeException e) {
                e.printStackTrace();
            }
        }
        balanceGap = AttrUtils.getDimensionFromAttr(attrSet, "msrv_balanceGap", dp2px(4));
        maxValue = AttrUtils.getIntFromAttr(attrSet, "msrv_maxValue", 50_000);
        currentValue = AttrUtils.getIntFromAttr(attrSet, "msrv_currentValue", 0);
        balanceValue = AttrUtils.getIntFromAttr(attrSet, "msrv_balanceValue", 0);
        valueUnit = AttrUtils.getIntFromAttr(attrSet, "msrv_valueUnit", 100);
        valuePerCount = AttrUtils.getIntFromAttr(attrSet, "msrv_valuePerCount", 10);
    }

    private int mDownX;
    private int mDownY;
    private int mLastX;
    private int mLastY;
    private boolean mIsMoving;

    /**
     * 滑动到最近的刻度上
     */
    private void scrollToGradation() {
        // 最近的刻度
        currentValue = Math.round(mCurrentDistance / unitGap) * valueUnit;
        // 校验边界
        currentValue = Math.min(maxValue, Math.max(0, currentValue));
        // 计算新刻度位置
        mCurrentDistance = currentValue / valueUnit * unitGap;
        logD("scrollToGradation: currentValue=%d, mCurrentDistance=%f", currentValue, mCurrentDistance);
        if (mListener != null) {
            mListener.onValueChanged(currentValue);
        }
        invalidate();
    }

    /**
     * 校验距离，并重新计算当前值
     */
    private void computeValue() {
        logD("computeValue: mRangeDistance=%d, mCurrentDistance=%f", mRangeDistance, mCurrentDistance);
        mCurrentDistance = Math.min(mRangeDistance, Math.max(0, mCurrentDistance));
        currentValue = (int) (mCurrentDistance / unitGap) * valueUnit;
        if (mListener != null) {
            mListener.onValueChanged(currentValue);
        }
        new EventHandler(EventRunner.getMainEventRunner())
                .postTask(
                        new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
    }

    /**
     * 绘制刻度、金额、及剩余额度
     *
     * @param canvas canvas
     */
    private void drawRule(Canvas canvas) {
        canvas.save();
        canvas.translate(0, gradationHeight);

        // 参考线
        mPaint.setStrokeWidth(gradationShortWidth);
        Point startPoint = new Point(0, 0);
        Point endPoint = new Point(mWidth, 0);
        canvas.drawLine(startPoint, endPoint, mPaint);

        // 刻度、数值
        final int expend = 3 * valueUnit;
        // 起始绘制刻度
        int start = (int) ((mCurrentDistance - mHalfWidth) / unitGap) * valueUnit;
        start = Math.max(0, start - expend);
        int end = Math.min(maxValue, (start + expend) + mWidthRangeValue + expend);
        float startOffset = mHalfWidth - (mCurrentDistance - start / valueUnit * unitGap);
        final int perCount = valuePerCount * valueUnit;
        // 剩余金额：向下取整
        final int balance = balanceValue / valueUnit * valueUnit;
        logD(
                "drawRule: mCurrentDistance=%f, start=%d, end=%d, startOffset=%f, perCount=%d",
                mCurrentDistance, start, end, startOffset, perCount);
        deal(canvas, start, end, startOffset, perCount, balance);

        canvas.restore();
    }

    private void deal(Canvas canvas, int start, int end, float startOffset, int perCount, int balance) {
        while (start <= end) {
            if (start % perCount == 0) {
                // 刻度
                mPaint.setStrokeWidth(gradationLongWidth);
                Point startPointGradation = new Point(startOffset, 0);
                Point endPointGradation = new Point(startOffset, -gradationLongLen);
                canvas.drawLine(startPointGradation, endPointGradation, mPaint);

                // 数值
                mTextPaint.setTextSize((int) gradationTextSize);
                mTextPaint.setColor(new Color(gradationTextColor));
                String text = Integer.toString(start);
                float textWidth = mTextPaint.measureText(text);
                canvas.drawText(
                        mTextPaint, text, startOffset - textWidth * .5f, -(gradationLongLen + gradationValueGap));
            } else {
                mPaint.setStrokeWidth(gradationShortWidth);
                Point startPointGradation = new Point(startOffset, 0);
                Point endPointGradation = new Point(startOffset, -gradationShortLen);
                canvas.drawLine(startPointGradation, endPointGradation, mPaint);
            }

            // 剩余金额
            if (start == balance) {
                mPaint.setColor(new Color(indicatorColor));
                Point startPointBalance = new Point(startOffset, 0);
                Point endPointBalance = new Point(startOffset, -gradationLongLen);
                canvas.drawLine(startPointBalance, endPointBalance, mPaint);
                mPaint.setColor(new Color(gradationColor));

                mTextPaint.setTextSize((int) balanceTextSize);
                mTextPaint.setColor(new Color(indicatorColor));
                float textWidth = mTextPaint.measureText(balanceText);
                canvas.drawText(mTextPaint, balanceText, startOffset - textWidth * .5f, balanceGap + balanceTextSize);
                mTextPaint.setColor(new Color(gradationColor));
            }

            start += valueUnit;
            startOffset += unitGap;
        }
    }


    /**
     * Draw indicator *
     *
     * @param canvas canvas
     */
    private void drawIndicator(Canvas canvas) {
        mPaint.setColor(new Color(indicatorColor));
        Point startPoint = new Point(mHalfWidth, 0);
        Point endPoint = new Point(mHalfWidth, gradationHeight);
        canvas.drawLine(startPoint, endPoint, mPaint);
        mPaint.setColor(new Color(gradationColor));
    }

    private int dp2px(float dp) {
        return DensityUtil.dp2px(getContext(), dp);
    }

    private int sp2px(float sp) {
        return DensityUtil.dp2px(getContext(), sp);
    }

    @SuppressWarnings("all")
    private void logD(String format, Object... args) {
        if (LOG_ENABLE) {
            LogUtil.info("MoneySelectRuleView", String.format("zjun@" + format, args));
        }
    }

    /**
     * Gets value *
     *
     * @return the value
     */
    public int getValue() {
        return currentValue;
    }

    /**
     * 设置值
     * 注意：这里不需要回调，否则会改变原数据
     *
     * @param value 当前金额
     */
    public void setValue(float value) {
        // 向下取整
        this.currentValue = (int) value / valueUnit * valueUnit;
        currentValue = Math.min(maxValue, Math.max(0, currentValue));
        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
        }
        if (mListener != null) {
            mListener.onValueChanged(currentValue);
        }
        calculateValues();
        new EventHandler(EventRunner.getMainEventRunner())
                .postTask(
                        new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
    }


    /**
     * Gets balance *
     *
     * @return the balance
     */
    public int getBalance() {
        return balanceValue;
    }


    /**
     * Sets balance *
     *
     * @param balance balance
     */
    public void setBalance(float balance) {
        this.balanceValue = (int) balance / valueUnit * valueUnit;
        new EventHandler(EventRunner.getMainEventRunner())
                .postTask(
                        new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        });
    }


    /**
     * Sets on value changed listener *
     *
     * @param listener listener
     */
    public void setOnValueChangedListener(OnValueChangedListener listener) {
        this.mListener = listener;
    }
}
